package br.ufc.ck0017.visitor.typechecking;

import br.ufc.ck0017.semantic.model.*;
import br.ufc.ck0017.syntaxtree.*;
import br.ufc.ck0017.visitor.DepthFirstVisitor;

import java.util.ArrayList;
import java.util.List;

public class SymbolTableVisitor extends DepthFirstVisitor {
    private SemanticClass currentClass;
    private SemanticMethod currentMethod;

    private final SemanticProgram program;
    private final List<String> errors;

    public SymbolTableVisitor() {
        program = new SemanticProgram();
        errors = new ArrayList<>();
    }

    public List<String> getErrorMessages() {
        return new ArrayList<>(errors);
    }

    public SemanticProgram getProgram() {
        return program;
    }

    // Identifier i1,i2;
    // IRStatement s;
    public Void visit(MainClass n) {

        try {
            String id = n.i1.toString();
            program.setSemanticMainClass(new SemanticMainClass(id));
        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_MAIN_CLASS);
        }

        try {
            String id = "main";
            SemanticMainClass mainClass = program.getSemanticMainClass();

            if (mainClass != null) {
                mainClass.setSemanticMainMethod(new SemanticMainMethod(id));
            }
        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_MAIN_METHOD);
        }

        String id = n.i2.toString();
        SemanticMainClass mainClass = program.getSemanticMainClass();

        if (mainClass != null) {
            SemanticMainMethod mainMethod = mainClass.getSemanticMainMethod();

            if (mainMethod != null) {
                mainMethod.setArgs(new SemanticMainMethodArgs(id));
            }
        }
        return null;
    }

    // Identifier i;
    // VarDeclList vl;
    // MethodDeclList ml;
    public Void visit(ClassDeclSimple n) {

        String id = n.i.toString();
        currentClass = new SemanticClass();

        try {
            program.addClass(id, currentClass);

            currentClass.setName(id);

            for (int i = 0; i < n.vl.size(); i++) {
                n.vl.elementAt(i).accept(this);
            }
            for (int i = 0; i < n.ml.size(); i++) {
                n.ml.elementAt(i).accept(this);
            }

        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_CLASS);
        }

        currentClass = null;
        return null;
    }

    // Identifier i;
    // Identifier j;
    // VarDeclList vl;
    // MethodDeclList ml;
    public Void visit(ClassDeclExtends n) {
        String id = n.i.toString();
        SemanticClass parentClass = new SemanticClass();
        parentClass.setName(n.j.toString());
        currentClass = new SemanticClass();

        try {
            currentClass.setParent(parentClass);

            program.addClass(id, currentClass);

            currentClass.setName(id);

            for (int i = 0; i < n.vl.size(); i++) {
                n.vl.elementAt(i).accept(this);
            }
            for (int i = 0; i < n.ml.size(); i++) {
                n.ml.elementAt(i).accept(this);
            }

        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_CLASS);
        }

        currentClass = null;
        return null;
    }

    // Type t;
    // Identifier i;
    public Void visit(VarDecl n) {
        Type type = n.t;
        String id = n.i.toString();

        try {
            // Inside a method's scope.
            if (currentMethod != null) {
                currentMethod.addVar(id, new SemanticVariable(type));
            }
        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_VAR);
        }

        try {
            // Inside a class' scope.
            if (currentMethod == null && currentClass != null) {
                currentClass.addField(id, new SemanticVariable(type));
            }
        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_FIELD);
        }

        return null;
    }

    // Type t;
    // Identifier i;
    // FormalList fl; (method parameters)
    // VarDeclList vl;
    // StatementList sl;
    // Exp e;
    public Void visit(MethodDecl n) {

        try {
            Type type = n.t;
            String id = n.i.toString();

            currentMethod = new SemanticMethod(type);

            if (currentClass != null) {
                currentClass.addMethod(id, currentMethod);

                currentMethod.setName(id);

                for (int i = 0; i < n.fl.size(); i++) {
                    n.fl.elementAt(i).accept(this);
                }
                for (int i = 0; i < n.vl.size(); i++) {
                    n.vl.elementAt(i).accept(this);
                }
                for (int i = 0; i < n.sl.size(); i++) {
                    n.sl.elementAt(i).accept(this);
                }
                n.e.accept(this);

            }
        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_METHOD);
        }

        currentMethod = null;
        return null;
    }

    // Type t;
    // Identifier i;
    public Void visit(Formal n) {
        try {
            Type type = n.t;
            String id = n.i.toString();

            currentMethod.addParameter(id, new SemanticVariable(type));
        } catch (IllegalArgumentException e) {
            errors.add(ErrorMessage.DUPLICATE_PARAMETER);
        }
        return null;
    }

    @Override
    public String toString() {
        return "SymbolTableVisitor{" +
                "currentClass=" + currentClass +
                ", currentMethod=" + currentMethod +
                ", program=" + program +
                ", errors=" + errors +
                '}';
    }
}
